package com.digua.base.socket

import com.alibaba.fastjson2.JSON
import com.digua.base.socket.request.Request
import com.digua.base.socket.response.ErrorResponse
import com.digua.base.socket.response.Response
import com.digua.base.socket.response.ResponseFactory
import com.digua.base.util.LogUtil
import com.digua.base.util.StrUtil
import org.java_websocket.WebSocket
import org.java_websocket.client.WebSocketClient
import org.java_websocket.drafts.Draft
import org.java_websocket.drafts.Draft_6455
import org.java_websocket.exceptions.WebsocketNotConnectedException
import org.java_websocket.framing.Framedata
import org.java_websocket.handshake.ServerHandshake
import java.net.URI
import java.nio.ByteBuffer


class WebSocketWrapper {
    private var mSetting: WebSocketSetting? = null
    private var mSocketListener: SocketWrapperListener? = null
    private var mWebSocket: WebSocketClient? = null

    /**
     * 0-未链接
     * 1-正在链接
     * 2-已链接
     */
    private var connectStatus = 0

    /**
     * 需要关闭链接标志，调用 #disconnect 方法后为 true
     */
    private var needClose = false

    /**
     * 是否已销毁
     */
    private var destroyed = false

    constructor(setting: WebSocketSetting?, socketListener: SocketWrapperListener?) {
        mSetting = setting
        mSocketListener = socketListener
    }

    fun connect() {
        if (destroyed) return
        needClose = false
        if (connectStatus == 0) {
            connectStatus = 1
            try {
                if (mWebSocket == null) {
                    mSetting?.let { setting ->
                        if (StrUtil.isBlank(setting.connectUrl)) {
                            connectStatus = 0
                            throw RuntimeException("WebSocket 链接url不能为空!")
                        }
                        var draft: Draft? = setting.draft
                        if (draft == null) draft = Draft_6455()
                        val connectTimeOut = if (setting.connectTimeout < 0) 0L else setting.connectTimeout
                        mWebSocket = MyWebSocketClient(this, URI(setting.connectUrl), setting.httpHeaders, draft, connectTimeOut.toInt())
                        LogUtil.d("WebSocket 开始链接中...")
                        mWebSocket?.apply {
                            if (setting.mProxy != null) setProxy(setting.mProxy)
                            connect()
                            connectionLostTimeout = setting.connectionLostTimeout
                        }
                        if (needClose) disConnect()
                        checkDestroy()
                    }
                } else {
                    LogUtil.d("WebSocket 重连中...")
                    mWebSocket?.reconnect()
                    if (needClose) disConnect()
                    checkDestroy()
                }
            } catch (e: Throwable) {
                connectStatus = 0
                LogUtil.e("WebSocket 链接失败:", e)
                mSocketListener?.onConnectFailed(e)
            }
        }
    }

    /**
     * 重新链接
     */
    fun reconnect() {
        needClose = false
        if (connectStatus == 0) connect()
    }

    /**
     * 断开链接
     */
    fun disConnect() {
        needClose = true
        if (connectStatus == 2) {
            LogUtil.d("WebSocket 断开链接中...")
            mWebSocket?.close()
            LogUtil.d("WebSocket 断开链接成功")
        }
    }

    /**
     * 发送数据
     *
     * @param request 请求数据
     */
    fun send(request: Request<*>?) {
        if (mWebSocket == null) return
        if (request == null) {
            LogUtil.e("发送数据不能为空!")
            return
        }
        if (connectStatus == 2) {
            try {
                request.send(mWebSocket)
                LogUtil.d("发送成功:${JSON.toJSONString(request.getRequestData())}")
            } catch (e: WebsocketNotConnectedException) {
                connectStatus = 0
                LogUtil.e("socket链接已断开，发送失败:${JSON.toJSONString(request.getRequestData())}", e)
                mSocketListener?.apply {
                    onSendDataError(request, ErrorResponse.ERROR_NO_CONNECT, e)
                    onDisconnect()
                }
            } catch (e: Throwable) {
                connectStatus = 0
                LogUtil.e("异常，发送失败:${JSON.toJSONString(request.getRequestData())}", e)
                mSocketListener?.onSendDataError(request, ErrorResponse.ERROR_UNKNOWN, e)
            } finally {
                request.release()
            }
        } else {
            LogUtil.e("socket未链接，发送失败:${JSON.toJSONString(request.getRequestData())}")
            mSocketListener?.onSendDataError(request, ErrorResponse.ERROR_NO_CONNECT, null)
        }
    }

    /**
     * 获取链接状态
     * 0-未链接
     * 1-正在链接
     * 2-已链接
     */
    fun getConnectState() = connectStatus

    /**
     * 彻底销毁资源
     */
    fun destroy() {
        destroyed = true
        disConnect()
        if (connectStatus == 0) mWebSocket = null
        releaseResource()
    }

    private fun checkDestroy() {
        if (destroyed) {
            try {
                mWebSocket?.apply { if (!isClosed) close() }
                releaseResource()
            } catch (e: Throwable) {
                LogUtil.e("checkDestroy(WebSocketClient)", e)
            }
            connectStatus = 0
        }
    }

    private fun releaseResource() {
        mSocketListener = null
    }

    private fun onWSCallbackOpen(handshakeData: ServerHandshake) {
        if (destroyed) {
            checkDestroy()
            return
        }
        connectStatus = 2
        LogUtil.d("WebSocket 链接成功")
        if (needClose) {
            disConnect()
        } else {
            mSocketListener?.onConnected()
        }
    }

    private fun onWSCallbackMessage(message: String) {
        if (destroyed) {
            checkDestroy()
            return
        }
        connectStatus = 2
        mSocketListener?.let {
            val response: Response<String> = ResponseFactory.createTextResponse()
            response.setResponseData(message)
            LogUtil.d("WebSocket 收到消息:$response")
            it.onMessage(response)
        }
    }

    private fun onWSCallbackMessage(bytes: ByteBuffer) {
        if (destroyed) {
            checkDestroy()
            return
        }
        connectStatus = 2
        mSocketListener?.let {
            val response: Response<ByteBuffer> = ResponseFactory.createByteBufferResponse()
            response.setResponseData(bytes)
            LogUtil.d("WebSocket 收到消息:$response")
            it.onMessage(response)
        }
    }

    private fun onWSCallbackWebsocketPing(f: Framedata) {
        if (destroyed) {
            checkDestroy()
            return
        }
        connectStatus = 2
        mSocketListener?.let {
            val response: Response<Framedata> = ResponseFactory.createPingResponse()
            response.setResponseData(f)
            LogUtil.d("WebSocket 收到 ping:$response")
            it.onMessage(response)
        }
    }

    private fun onWSCallbackWebsocketPong(f: Framedata) {
        if (destroyed) {
            checkDestroy()
            return
        }
        connectStatus = 2
        mSocketListener?.let {
            val response = ResponseFactory.createPongResponse()
            response.setResponseData(f)
            LogUtil.d("WebSocket 收到 pong:$response")
            it.onMessage(response)
        }
    }

    private fun onWSCallbackClose(code: Int, reason: String, remote: Boolean) {
        connectStatus = 0
        LogUtil.d("WebSocket 关闭! code=${code},reason:${reason},remote:${remote}")
        mSocketListener?.onDisconnect()
        checkDestroy()
    }

    private fun onWSCallbackError(ex: Exception) {
        if (destroyed) {
            checkDestroy()
            return
        }
        LogUtil.e("WebSocketClient#onError(Exception)", ex)
    }

    private class MyWebSocketClient : WebSocketClient {
        val wrapper: WebSocketWrapper

        constructor(wrapper: WebSocketWrapper, serverUri: URI?, httpHeaders: MutableMap<String, String>? = null, protocolDraft: Draft = Draft_6455(), connectTimeout: Int = 0) : super(serverUri,
            protocolDraft,
            httpHeaders,
            connectTimeout) {
            this.wrapper = wrapper
        }


        override fun onOpen(handshakeData: ServerHandshake) {
            wrapper.onWSCallbackOpen(handshakeData)
        }

        override fun onMessage(message: String) {
            wrapper.onWSCallbackMessage(message)
        }

        override fun onMessage(bytes: ByteBuffer) {
            wrapper.onWSCallbackMessage(bytes)
        }

        override fun onWebsocketPing(conn: WebSocket, f: Framedata) {
            super.onWebsocketPing(conn, f)
            wrapper.onWSCallbackWebsocketPing(f)
        }

        override fun onWebsocketPong(conn: WebSocket, f: Framedata) {
            super.onWebsocketPong(conn, f)
            wrapper.onWSCallbackWebsocketPong(f)
        }

        override fun onClose(code: Int, reason: String, remote: Boolean) {
            wrapper.onWSCallbackClose(code, reason, remote)
        }

        override fun onError(ex: Exception) {
            wrapper.onWSCallbackError(ex)
        }
    }
}